package com.white.databasesbackup;

import com.white.databasesbackup.config.DatabasesBackupConfig;
import com.white.databasesbackup.dao.DatabasesBackupConfigureDao;
import com.white.databasesbackup.dao.DatabasesBackupLogDao;
import com.white.databasesbackup.entity.DatabasesBackupConfigure;
import com.white.databasesbackup.entity.DatabasesBackupLog;
import com.white.databasesbackup.entity.PageVO;
import com.white.databasesbackup.expand.RunnableTransactionalNotSupported;
import com.white.databasesbackup.expand.RunnableTransactionalSupported;
import com.white.databasesbackup.util.PageUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * <p>
 * 数据读取
 * </p>
 *
 * @author huangzy
 * @since 2022/5/6
 */
@Component
public class Backup {

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Resource
    private DatabasesBackupConfig databasesBackupConfig;

    @Resource
    private RunnableTransactionalNotSupported runnableTransactionalNotSupported;

    @Resource
    private RunnableTransactionalSupported runnableTransactionalSupported;

    @Resource
    private DatabasesBackupConfigureDao databasesBackupConfigureDao;

    @Resource
    private DatabasesBackupLogDao databasesBackupLogDao;

    public void backup(){
        //备份
        databasesBackupConfigureDao.list()
                .stream()
                .filter(DatabasesBackupConfigure::getEnable)
                .forEach(this::backup);
    }

    /**
     * 备份数据库数据
     * @param databasesBackupConfigure 数据库备份配置
     */
    public void backup(DatabasesBackupConfigure databasesBackupConfigure) {
        //事务执行
        runnableTransactionalSupported.run(() -> {
            //创建备份日志记录数据
            DatabasesBackupLog databasesBackupLog = new DatabasesBackupLog();
            databasesBackupLog.setCreateTime(LocalDateTime.now());
            databasesBackupLog.setDatabasesName(databasesBackupConfigure.getDatabasesName());
            databasesBackupLog.setTableName(databasesBackupConfigure.getTableName());
            long backupCount = 0;
            //错误信息
            Throwable throwable = null;
            //获取备份数据存储路径
            File backupFile = getBackupFile(databasesBackupConfigure);
            try (FileWriter writer = new FileWriter(backupFile,true);
                 PrintWriter printWriter = new PrintWriter(writer,true)){
                //备份并获取备份的条目数
                backupCount = readAndDelete(databasesBackupConfigure, rows -> {
                    //获取所需备份数据的插入语句
                    List<String> insertSqls = rows.stream()
                            .map(item -> new InsertSql(
                                    databasesBackupConfigure.getDatabasesName(),
                                    databasesBackupConfigure.getTableName(),
                                    item).toSql())
                            .collect(Collectors.toList());
                    //输出到硬盘
                    if (databasesBackupConfigure.getBackup()) {
                        insertSqls.forEach(printWriter::println);
                    }
                });
                //封装备份日志
                if (!databasesBackupConfigure.getBackup()) {
                    //如果无需备份则备份数量设置为0
                    databasesBackupLog.setCount(0L);
                }else {
                    databasesBackupLog.setCount(backupCount);
                }
                databasesBackupLog.setDeleteCount(backupCount);
                databasesBackupLog.setExecStatus(true);
            } catch (Throwable e) {
                throwable = e;
                //存储失败或者其他报错,则回滚,并且删除备份的文件
                if (backupFile.exists()) {
                    backupFile.delete();
                }
                //封装备份失败日志
                databasesBackupLog.setFailMsg(e.getMessage());
                databasesBackupLog.setExecStatus(false);
            }
            if (backupCount > 0 || (databasesBackupLog.getExecStatus() != null && !databasesBackupLog.getExecStatus())) {
                //记录日志且以非事务方式执行
                try {
                    runnableTransactionalNotSupported.run(() -> databasesBackupLogDao.save(databasesBackupLog));
                } catch (Throwable e) {
                    //存储失败或者其他报错,则回滚,并且删除备份的文件
                    if (backupFile.exists()) {
                        backupFile.delete();
                    }
                    throw e;
                }
            }
            if (throwable != null) {
                throw new RuntimeException("备份文件时出错: ,已执行回滚操作"+throwable.getMessage(),throwable);
            }
        });
    }

    /**
     * 读取数据，分页读取
     * 读取完毕后删除数据
     *
     * @param databasesBackupConfigure 数据库备份配置
     * @param consumer                 每页数据处理方法
     * @return 需要备份的数据条目数
     */
    private long readAndDelete(DatabasesBackupConfigure databasesBackupConfigure, Consumer<List<Map<String, Object>>> consumer) {
        //查询数据语句
        String selectSql = "SELECT * FROM `" + databasesBackupConfigure.getDatabasesName() + "`.`" + databasesBackupConfigure.getTableName() + "` ";
        //查询数量语句
        String countSql = "SELECT COUNT(1) FROM `" + databasesBackupConfigure.getDatabasesName() + "`.`" + databasesBackupConfigure.getTableName() + "` ";
        //条件语句
        String dateCondition;
        if (!databasesBackupConfigure.getTimestamp()) {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:ss:mm");
            dateCondition = LocalDateTime.now().plusDays(-databasesBackupConfigure.getRetentionTime()).format(formatter);
        } else if (databasesBackupConfigure.getTimestampUnit() == 2) {
            dateCondition = String.valueOf(System.currentTimeMillis() / 1000L - databasesBackupConfigure.getRetentionTime() * 24 * 60 * 60);
        } else {
            dateCondition = String.valueOf(System.currentTimeMillis() - databasesBackupConfigure.getRetentionTime() * 24 * 60 * 60 * 1000L);
        }
        String whereSql = " WHERE `" + databasesBackupConfigure.getDateFieldName() + "` < '" + dateCondition + "'";
        //删除语句
        String deleteSql = "DELETE FROM `" + databasesBackupConfigure.getDatabasesName() + "`.`" + databasesBackupConfigure.getTableName() + "` ";
        //查询总数量
        Long count = jdbcTemplate.queryForObject(countSql, Long.class);
        Long backupCount = null;
        //满足条件才备份
        if (count != null && count >= databasesBackupConfigure.getDataThreshold()) {
            //查询需要备份的数量
            backupCount = jdbcTemplate.queryForObject(countSql + whereSql, Long.class);
            //备份数量大于0才备份
            if (backupCount != null && backupCount > 0) {
                //定义每页显示条目数
                int size = 1000;
                Long finalBackupCount = backupCount;
                PageUtil.executeAll(page -> {
                    String sql = selectSql + whereSql + " LIMIT " + (page - 1) * size + " , " + size;
                    //获取结果集
                    List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql);
                    //结果集处理
                    consumer.accept(rows);
                    //封装分页数据
                    PageVO<?> result = new PageVO<>();
                    result.setPage(page);
                    result.setSize(size);
                    result.setTotal(Math.toIntExact(finalBackupCount));
                    return result;
                });
                //删除数据
                jdbcTemplate.update(deleteSql + whereSql);
            }
        }
        return backupCount == null ? 0 : backupCount;
    }

    /**
     * 获取这次备份文件的文件路径
     * @param databasesBackupConfigure 数据库备份配置
     * @return 准备存储的文件路径
     */
    private File getBackupFile(DatabasesBackupConfigure databasesBackupConfigure){
        //存储路径
        String path = databasesBackupConfig.getPath();
        if (StringUtils.isEmpty(path)) {
            throw new RuntimeException("数据库备份路径未在配置文件中填写");
        }
        //进入备份存储根目录
        File file = new File(path);
        createMkdir(file);
        //获取年月日
        LocalDateTime now = LocalDateTime.now();
        //创建年目录
        file = new File(file, String.valueOf(now.getYear()));
        createMkdir(file);
        //创建月目录
        file = new File(file, String.valueOf(now.getMonthValue()));
        createMkdir(file);
        //创建日目录
        file = new File(file, String.valueOf(now.getDayOfMonth()));
        createMkdir(file);
        //创建存储文件
        file = new File(file, databasesBackupConfigure.getDatabasesName() + "_" + databasesBackupConfigure.getTableName() + "_" + System.currentTimeMillis() + ".sql");
        createFile(file);
        return file;
    }

    /**
     * 创建这个File目录
     * @param file file
     */
    private void createMkdir(File file){
        if (!file.exists()) {
            boolean mkdirs = file.mkdirs();
            if (!mkdirs) {
                throw new RuntimeException("数据库备份文件目录创建失败");
            }
        }
    }

    /**
     * 创建这个File文件
     * @param file file
     */
    private void createFile(File file){
        if (!file.exists()) {
            boolean mkdirs;
            try {
                mkdirs = file.createNewFile();
                if (!mkdirs) {
                    throw new RuntimeException("数据库备份文件目录创建失败");
                }
            } catch (IOException e) {
                throw new RuntimeException("数据库备份文件创建失败",e);
            }
        }
    }
}
